第5章 Triangulation by Ear Clipping

5.1  Introduction

In this chapter, we will explain one of the methods of dividing polygons into triangles, the "ear clipping method", hereinafter referred to as the "ear clipping method". In addition to the usual simple polygon triangulation, we will also explain the triangular division of polygons with holes and polygons that have a hierarchical structure.

The sample in this chapter is "TriangulationByEarClipping" from
https://github.com/IndieVisualLab/UnityGraphicsProgramming4
.

5.1.1  How to operate the sample

Run the sample DrawTest scene. Left-click on GameView to make a dot on the screen. Continue left-clicking on another point to connect it with the first point with a line. If you repeat it, you will get a polygon. When drawing lines, be careful not to cross the lines. Right-click to split the polygon into triangles to generate a mesh. If you generate a polygon in the generated mesh, you will get a polygon with holes.

Screen where the sample was executed

Figure 5.1: Screen of running the sample

5.2  Simple polygon triangulation

A simple polygon is a closed polygon that does not intersect at its own line segment.

Left: Simple polygon, Right: Non-simple polygon

Figure 5.2: Left: Simple polygon, Right: Non-simple polygon

Any simple polygon can be triangulated. Dividing a simple polygon with n vertices into triangles creates n-2 triangles.

5.3  Ear cutting method (EarClipping method)

There are many methods for dividing polygons into triangles, but this time we will explain the "ear cutting method", which is simple to implement. The "ear-cutting method" is divided based on the theorem "Two ears the orem". This "Ear" refers to "a triangle whose two sides are polygonal sides and the remaining one side exists inside the polygon", and this theorem states that "four or more sides". A simple polygon without a hole with has at least two ears. "

ear

Figure 5.3: Ears

The "ear trimming method" is an algorithm that searches for this "ear" triangle and removes it from the polygon. This "ear cutting method" is simpler than other division algorithms, but it is slow, so I don't think it can be used very much in situations where speed is required.

5.3.1  Flow of triangle division

First, look for "ears" in the given array of polygon vertices. The conditions for "ears" are the following two points.

Ear conditions (within 180 degrees, no other vertices in the triangle)

Figure 5.4: Ear conditions (within 180 degrees, no other vertices in the triangle)

Add the vertex vi that meets the above conditions to the ear list. This is done by the InitializeVertices function in the sample Triangulation.cs. Then create the triangles that make up the ear from the top of the ear list and remove the vertex vi from the vertex array.
Removing the vertex vi changes the shape of the polygon. For the remaining vertices vi-1, vi + 1, perform the above ear judgment again. If vertices vi-1, vi + 1 meet the ear criteria, they will be added to the end of the ear list, but they may also be removed from the ear list. This process corresponds to the CheckVertex function and EarClipping function of the sample Triangulation.cs.

Polygon before deleting vertex vi and polygon after deleting

Figure 5.5: Polygon before and after deleting vertex vi

Let's illustrate a series of flows using a simple polygon as an example.

A simple polygon

Figure 5.6: A simple polygon

First, look for your ears. In this case, the ear list contains vertices 0,1,4,6. Vertices 2 and 5 are excluded because they are not convex vertices, and vertices 3 are excluded because they are contained in triangles 2, 3 and 4.
First, take out the first vertex 0 of the ear list. Make a triangle with vertices 1 and 6 before and after vertex 0. Remove vertex 0 from the vertex array and connect the previous and next vertices 1 and 6 to form a new polygon. Then, the ears are judged for vertices 1 and 6. Originally both were ears, but they remain ears even after the ear judgment. The ear list at this time is 1,4,6.

Polygon with 0 vertices removed

Figure 5.7: Polygon with 0 vertices removed

Then take vertex 1 from the beginning of the ear list. Make a triangle with vertices 2 and 6 before and after vertex 1. Remove vertex 1 from the vertex array and connect the previous and next vertices 2 and 6 to form a new polygon. Then, the ears are judged for vertices 2 and 6. Since the vertex 1 is gone, the vertex 2 becomes a convex vertex and the ear condition is satisfied, so add it to the ear list. Vertex 6 remains in the ear. The ear list at this time is 4,6,2.

Polygon with vertex 1 removed

Figure 5.8: Polygon with vertex 1 removed

Then take vertex 4 from the top of the ear list. Make a triangle with vertices 3 and 5 before and after vertex 4. Remove vertex 4 from the vertex array and connect the previous and next vertices 3 and 5 to form a new polygon. Then, the ears are judged for vertices 3 and 5. With the disappearance of vertex 4, the triangle created by vertices 2 and 5 before and after vertex 3 no longer contains other vertices, so add vertex 3 to the ear list. Also, since the internal angle of vertex 5 is 180 degrees or less, it becomes a convex vertex and the ear condition is satisfied, so add it to the ear list. The ear list at this time is 6,2,3,5.

Polygon with vertex 4 removed

Figure 5.9: Polygon with vertex 4 removed

Then take vertex 6 from the top of the ear list. Make a triangle with vertices 2 and 5 before and after vertex 6. Remove vertex 6 from the vertex array and connect the previous and next vertices 2 and 5 to form a new polygon. Then, the ears are judged for vertices 2 and 5. Originally both were ears, but they remain ears even after the ear judgment. The ear list at this time is 2,3,5.

Polygon with vertex 6 removed

Figure 5.10: Polygon with vertex 6 removed

Next, I took out vertex 2 from the top of the ear list ... I thought, but since there are only 3 vertices of the polygon left, I made a triangle as it is and the triangle division is finished. The final result of the triangle split is:

Result of triangle division

Figure 5.11: Triangular division result

5.4  Perforated polygonal triangle division

Next, I will explain the triangular division of a polygon with holes. Originally, the "ear cutting method" cannot be applied to polygons with holes, but if you make a notch in the outer polygon and connect it to the inner polygon as shown in the figure, the inner polygon will be the outer polygon. It will be part of the and you will be able to apply the ear-cutting method. This method is also possible for polygons with multiple holes.

Joining inner and outer polygons (the figure is a fairly exaggerated expression)

Figure 5.12: Joining inner and outer polygons (figure is a fairly exaggerated representation)

5.4.1  Flow of joining outer polygon and inner polygon

As a premise, the order of the vertices of the outer and inner polygons must be reversed. For example, if the outer polygon has vertices aligned clockwise, the inner polygon must align counterclockwise. The flow of joining is explained using the following polygon as an example.

Polygon with holes

Figure 5.13: Polygon with holes

 1. If there are multiple holes (inner polygons), look for the polygon with the largest X coordinate (on the right) and its vertices among the inner polygons.

Vertex with the largest X coordinate

Figure 5.14: Vertex with the highest X coordinate

 2. Let M be the vertex with the largest X coordinate. Draw a straight line from M to the right.

Draw a line to the right from vertex M

Figure 5.15: Draw a line to the right from vertex M

 3. Find the edge and intersection I of the outer polygon that intersects the line extending to the right from vertex M. If it intersects multiple sides, select the side of the intersection closest to vertex M.

Vertex M and intersection I

Figure 5.16: Vertex M and intersection I

 4. Select the vertex P with the largest X coordinate among the vertices of the intersecting sides. Check if the triangle connecting the vertices M, I, P contains other vertices.

Triangle M, I, P

Figure 5.17: Triangle M, I, P

 5. If the triangles M, I, P do not contain other vertices, it can be divided, so connect the vertices P of the outer polygon to the vertices M of the inner polygon, and turn the inner polygon counterclockwise. I will go around. When connecting from M to the vertex P of the outer polygon again, the vertex M and the vertex P are duplicated to make another vertex (vertex M', P'). By separating the incoming line and the outgoing line, the lines seem to overlap, but the order of the vertices is a simple polygon that does not intersect.

Diagram of connecting the outer polygon and the inner polygon

Figure 5.18: A diagram connecting the outer polygon and the inner polygon

 6. If the triangles M, I, P contain other vertices R, select that vertex R, but if multiple vertices are included, line segments M, I and line segment M Select the vertex R with the smallest angle θ formed by, R, and perform processing 5.

Vertex R with the smallest angle θ formed by line segments MI and MR

Figure 5.19: Vertex R with the smallest angle θ formed by line segments MI and MR

 7. Go back to 1 and join with the other inner polygons.

5.5  Nested polygon triangulation

Next, I will explain the triangular division of a nested polygon. Since the process of joining polygons with holes and the process of dividing triangles were explained in the previous section, here we will mainly explain the procedure for building a tree of parent-child relationships of polygons.

  1. Sorts in descending order of area when the polygon is a rectangular area. The area of ​​the rectangular area created by the vertices of the minimum / maximum coordinates of the polygon.
  2. Recursively determines whether other polygons include all vertices in a polygon with a large area, and creates a tree of parent-child relationships. At this time, the top-level route is an empty polygon (so-called dummy) and is not used for the subsequent combination processing of polygons. The reason for making the top level a dummy is that if multiple sets of completely uncovered polygons are passed, the top level will not be one. You can simplify the process by hanging multiple polygons that do not overlap at all below the top of the dummy. Also, when the hierarchy is an even-numbered floor, it becomes an inner polygon, so the vertex array of the corresponding polygon is rearranged counterclockwise.
  3. Once you have a parent-child relationship tree, take out one polygon from the top. It becomes the outer polygon.
  4. Extracts the polygon one level below (child) of the outer polygon. Then, as an inner polygon, it is combined with the outer polygon to perform triangular division. If there are no children, divide it into triangles as it is.
  5. Go back to 3 and repeat the join and split. In 4, the outer polygon and the inner polygon group are processed as one combination, so the next triangle to be extracted will be the outer polygon again.

Let's take the following set of polygons as an example.

Nested polygon

Figure 5.20: Nested polygons

When you create a polygonal parent-child relationship, you get the following tree.

Left: Nested polygon Right: Parent-child relationship

Figure 5.21: Left: Nested polygon Right: Parent-child relationship

Extract polygon 1 at the top of the tree (excluding dummies) and polygons 2 and 4 of its children.

Take out polygons 1, 2 and 4

Figure 5.22: Extract polygons 1, 2 and 4

Join polygons 1, 2 and 4 in order from the right.

Combine polygons 1, 2 and 4

Figure 5.23: Joining polygons 1, 2 and 4

Divide the combined polygon into triangles.

Triangulate the combined polygons

Figure 5.24: Triangulation of combined polygons

Remove the polygon that is divided into triangles. The rest of the parent-child relationship tree is 3 and 5. First, take it out from 3.

Polygon 3

Figure 5.25: Polygon 3

Since 3 has no children, it is divided into triangles as it is.

Divide polygon 3 into triangles

Figure 5.26: Polygon 3 divided into triangles

Remove the polygon that is divided into triangles. There are only 5 left in the parent-child relationship tree. 5 is a triangle and has no children, so it ends as it is. This completes the triangular division of the nested polygon.

Last polygon 5

Figure 5.27: Last polygon 5

5.6  Implementation

Let's move on to the explanation of the sample source code that implements all three algorithms explained so far.

5.6.1  Polygon class

First, define a Polygon class that manages an array of polygon vertices. The Polygon class holds information such as the array of vertex coordinates and the direction of the loop, and determines whether a polygon is included in the polygon.

Polygon.cs

public class Polygon
{
    // Loop direction
    public enum LoopType
    {
        CW, // clockwise
        CCW, // counterclockwise
        ERR, // Indefinite (no orientation)
    }

    public Vector3 [] vertices; // Vertex array
    public LoopType loopType; // Loop direction

    // ~ omitted ~
}

5.6.2  Triangulation class

This is a Triangulation class that actually divides a polygon into triangles. The main is the Triangulate function of the Triangulation class.

Data structure used for triangle division

Data structure definition in Triangulation.cs

// Vertex array
List<Vector3> vertices = new List<Vector3>();

// List of vertex numbers (let's end and start connected)
LinkedList<int> indices = new LinkedList<int>();

// Ear vertex list
List<int> earTipList = new List<int>();

It defines vertices that stores the array of vertex coordinates of the polygon to be processed, indices that stores the number (index) of the vertices of the polygon, and earTipList that stores the ears. Since indices need to refer to the vertices before and after, we use LinkedList, which has the property of a bidirectional list.

Create a hierarchical structure

First, if you are given an array of vertices that make up a polygon from the outside, store it in the list as a Polygon class.

Polygon list

// Polygon list
List<Polygon> polygonList = new List<Polygon>();

public void AddPolygon(Polygon polygon)
{
    polygonList.Add(polygon);
}

At the beginning of the Triangulate function, sort the Polygon list with polygonal data added in descending order of area of ​​the rectangular area.

Sorted part of Polygon list

// Sort in descending order of area of ​​rectangular area in polygon list
polygonList.Sort((a, b) => Mathf.FloorToInt(
  (b.rect.width * b.rect.height) - (a.rect.width * a.rect.height)
  ));

Next, we will pack the sorted Polygon list into the TreeNode class that creates the tree structure.

The part that packs the Polygon list into a TreeNode

// Create route (empty)
polygonTree = new TreeNode<Polygon>();

// Create a polygonal hierarchy
foreach (Polygon polygon in polygonList)
{
   TreeNode<Polygon> tree = polygonTree;

   CheckInPolygonTree(tree, polygon, 1);
}

The TreeNode looks like this: I think it's a common tree structure, but for an empty top-level node, it defines a flag isValue for the existence of its contents.

TreeNode.cs

public class TreeNode<T>
{
    public TreeNode<T> parent = null;
    public List<TreeNode<T>> children = new List<TreeNode<T>>();

    public T Value;
    public bool isValue = false;

    public TreeNode(T val)
    {
        Value = val;
        isValue = true;
    }

    public TreeNode()
    {
        isValue = false;
    }

    public void AddChild(T val)
    {
        AddChild(new TreeNode<T>(val));
    }

    public void AddChild(TreeNode<T> tree)
    {
        children.Add(tree);
        tree.parent = this;
    }

    public void RemoveChild(TreeNode<T> tree)
    {
        if (children.Contains(tree))
        {
            children.Remove(tree);
            tree.parent = null;
        }
    }
}

Returning to Triangulation.cs, it is the contents of the CheckInPolygonTree function that creates a hierarchical structure of polygons. It checks whether the passed polygon fits inside its own polygon, and recursively determines whether it fits inside its own children. Makes the passed polygon its own child if it is included in itself but not in its children, or if there are no children.

CheckInPolygonTree関数

bool CheckInPolygonTree(TreeNode<Polygon> tree, Polygon polygon, int lv)
{
    // Does it have a polygon?
    bool isInChild = false;
    if (tree.isValue)
    {
        if (tree.Value.IsPointInPolygon(polygon))
        {
            // If it is included in itself, search if it is also included in the child
            for(int i = 0; i < tree.children.Count; i++)
            {
                isInChild |= CheckInPolygonTree(
                    tree.children[i], polygon, lv + 1);
            }

            // Make it your own child if it is not included in the child
            if (!isInChild)
            {
                // Invert the order of the vertices if necessary
                // CW because it is Inner when even nesting
                // CCW because it is Outer when odd nesting
                if (
                    ((lv % 2 == 0) &&
                     (polygon.loopType == Polygon.LoopType.CW)) ||
                    ((lv % 2 == 1) &&
                     (polygon.loopType == Polygon.LoopType.CCW))
                    )
                {
                    polygon.ReverseIndices();
                }

                tree.children.Add(new TreeNode<Polygon>(polygon));
                return true;
            }
        }
        else
        {
            // not included
            return false;
        }
    }
    else
    {
        // Search only for children if they have no value
        for (int i = 0; i < tree.children.Count; i++)
        {
            isInChild |= CheckInPolygonTree(
                tree.children[i], polygon, lv + 1);
        }

        // Make it your own child if it is not included in the child
        if (!isInChild)
        {
            // Invert the order of the vertices if necessary
            // CW because it is Inner when even nesting
            // CCW because it is Outer when odd nesting
            if (
                ((lv % 2 == 0) &&
                 (polygon.loopType == Polygon.LoopType.CW)) ||
                ((lv % 2 == 1) &&
                 (polygon.loopType == Polygon.LoopType.CCW))
                )
            {
                polygon.ReverseIndices();
            }
            tree.children.Add(new TreeNode<Polygon>(polygon));
            return true;
        }
    }

    return isInChild;
}

Processing of polygons with holes (combination of inner and outer polygons)

If there are multiple inner polygons, select the vertex with the largest X coordinate among the inner polygons and that polygon. At that time, define a class that collects the X coordinate, vertex number, and polygon number information for judgment.

XMaxData structure

/// <summary>
/// X coordinate maximum value and polygon information
/// </summary>
struct XMaxData
{
    public float xmax; // x coordinate maximum value
    public int no; // Polygon number
    public int index; // vertex number of xmax

    public XMaxData(float x, int n, int ind)
    {
        xmax = x;
        no = n;
        index = ind;
    }
}

Next, the actual joining process is divided into two processes: sorting multiple polygons in descending order of X coordinate, and joining. The first is the process of sorting multiple polygons in descending order of X coordinate.

CombineOuterAndInners関数

Vector3[] CombineOuterAndInners(Vector3[] outer, List<Polygon> inners)
{
    List<XMaxData> pairs = new List<XMaxData>();

    // Find the inner polygon with the vertex with the largest X coordinate
    for (int i = 0; i < inners.Count; i++)
    {
        float xmax = inners[i].vertices[0].x;
        int len = inners[i].vertices.Length;
        int xmaxIndex = 0;
        for (int j = 1; j < len; j++)
        {
            float x = inners[i].vertices[j].x;
            if(x > xmax)
            {
                xmax = x;
                xmaxIndex = j;
            }
        }
        pairs.Add(new XMaxData(xmax, i, xmaxIndex));
    }

    // Sort to the right (in descending order of xmax)
    pairs.Sort((a, b) => Mathf.FloorToInt(b.xmax - a.xmax));

    // Combine from right
    for (int i = 0; i < pairs.Count; i++)
    {
        outer = CombinePolygon(outer, inners[pairs[i].no], pairs[i].index);
    }

    return outer;
}

Next is the join processing part. In the CombinePolygon function, draw a horizontal line from the vertex M with the largest X coordinate of the inner polygon and find the line segment of the outer polygon that intersects that line.

Early stage of CombinePolygon function

Vector3[] CombinePolygon(Vector3[] outer, Polygon inner, int xmaxIndex)
{
    Vector3 M = inner.vertices[xmaxIndex];

    // Find the intersection
    Vector3 intersectionPoint = Vector3.zero;
    int index0 = 0;
    int index1 = 0;

    if (GeomUtil.GetIntersectionPoint(M,
      new Vector3(maxX + 0.1f, M.y, M.z),
      outer, ref intersectionPoint,
      ref index0, ref index1))
    {
      ~ Omitted ~

GeometryUtil.GetIntersectionPoint, a function that finds the intersection of line segments M and I and the line segment of the outer polygon, is as follows. The point is that the outer polygon is clockwise, so we only look for those whose starting point is above the line segments M and I and whose end point is below. Doing so will prevent the vertices from getting out of order if you select a line segment that connects the outer polygon to the inner polygon in the already joined inner and outer polygons.

GetIntersectionPoint function

public static bool GetIntersectionPoint(Vector3 start, Vector3 end,
                                        Vector3[] vertices,
                                        ref Vector3 intersectionP,
                                        ref int index0, ref int index1)
{
    float distanceMin = float.MaxValue;
    bool isHit = false;

    for(int i = 0; i < vertices.Length; i++)
    {
        int index = i;
        int next = (i + 1)% vertices.Length; // Next vertex

        Vector3 iP = Vector3.zero;
        Vector3 vstart = vertices[index];
        Vector3 vend = vertices[next];

        // The starting point of the intersecting polygonal line segment must be at least the line segment M, I
        Vector3 diff0 = vstart - start;
        if (diff0.y < 0f)
        {
            continue;
        }

        // The end point of the intersecting polygonal line segment is below the line segment M, I
        Vector3 diff1 = vend - start;
        if (diff1.y > 0f)
        {
            continue;
        }

        if (IsIntersectLine(start, end, vstart, vend, ref iP))
        {
            float distance = Vector3.Distance(start, iP);

            if (distanceMin >= distance)
            {
                distanceMin = distance;
                index0 = index;
                index1 = next;
                intersectionP = iP;
                isHit = true;
            }
        }

    }

    return isHit;
}

After finding the intersection, check if the triangle created from the vertex with the largest X coordinate of the intersecting line segment, vertex M, and intersection I contains other vertices. To determine if a triangle contains vertices, a two-dimensional cross product is used to determine which side of the triangle's line segment the vertices are on. If the vertices are to the right of all lines, they are inside the triangle.

IsTriangle and CheckLine functions in GeometryUtil.cs

/// <summary>
/// Returns the positional relationship between the line and the vertex
/// </summary>
/// <param name="o"></param>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns> +1: Right of line -1: Left of line 0: On line </ returns>
public static int CheckLine(Vector3 o, Vector3 p1, Vector3 p2)
{
    double x0 = o.x - p1.x;
    double y0 = o.y - p1.y;
    double x1 = p2.x - p1.x;
    double y1 = p2.y - p1.y;

    double x0y1 = x0 * y1;
    double x1y0 = x1 * y0;
    double det = x0y1 - x1y0;

    return (it> 0D? +1: (it <0D? -1: 0));
}

/// <summary>
/// Triangle (clockwise) and point inside / outside judgment
/// </summary>
/// <param name="o"></param>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <param name="p3"></param>
/// <returns> +1: outside -1: inside 0: online</returns>
public static int IsInTriangle(Vector3 o,
                               Vector3 p1,
                               Vector3 p2,
                               Vector3 p3)
{
    int sign1 = CheckLine(o, p2, p3);
    if (sign1 < 0)
    {
        return +1;
    }

    int sign2 = CheckLine(o, p3, p1);
    if (sign2 < 0)
    {
        return +1;
    }

    int sign3 = CheckLine(o, p1, p2);
    if (sign3 < 0)
    {
        return +1;
    }

    return (((sign1 != 0) && (sign2 != 0) && (sign3 != 0)) ? -1 : 0);
}

Now, the continuation of Combine Polygon. After finding the intersection, it is judged whether there are other vertices in the triangle, but the direction of the connection of the triangle is clockwise because the inside and outside judgment is made using the outer product.

Middle 1 of CombinePolygon function

if (GeomUtil.GetIntersectionPoint(M,
  new Vector3(maxX + 0.1f, M.y, M.z), outer,
  ref intersectionPoint, ref index0, ref index1))
{
    // Intersection found

    // Get the rightmost vertex of the intersecting line segment
    int pindex;
    Vector3[] triangle = new Vector3[3];
    if (outer[index0].x > outer[index1].x)
    {
        pindex = index0;
        // The triangle will be reversed depending on the vertex of the selected line segment, so adjust it so that it is clockwise.
        triangle[0] = M;
        triangle[1] = outer[pindex];
        triangle[2] = intersectionPoint;
    }
    else
    {
        pindex = index1;
        triangle[0] = M;
        triangle[1] = intersectionPoint;
        triangle[2] = outer[pindex];
    }

If the intersection I and the vertex with the largest X coordinate of the line segment are the same, there is nothing to block from the vertex M, so it is not checked whether the triangle contains other vertices. If they are not the same, it is checked whether other vertices are included, but since the vertices included in the triangle are recessed vertices, the inclusion judgment is performed while satisfying that condition. If the triangle contains multiple vertices, the vertex with the smallest angle between the line segments M and I and the line segment M and the corresponding vertex is selected and stored in the finalIndex.

Middle 2 of CombinePolygon function

Vector3 P = outer[pindex];

int finalIndex = pindex;

// If the intersection and P are the same, there is nothing to block, so do not check the triangle
if((Vector3.Distance(intersectionPoint, P) > float.Epsilon))
{
    float angleMin = 361f;

    for(int i = 0; i < outer.Length; i++)
    {

        // Convex vertex / Reflective vertex check
        int prevIndex = (i == 0)? outer.Length --1: i --1; // Previous vertex
        int nextIndex = (i + 1)% outer.Length; // Next vertex
        int nowIndex = i;

        if (nowIndex == pindex) continue;

        Vector3 outerP = outer[nowIndex];

        if (outerP.x < M.x) continue;

        // Ignore if the coordinates are the same duplicated at the time of division
        if (Vector3.Distance(outerP, P) <= float.Epsilon) continue;

        Vector3 prevVertex = outer[prevIndex];
        Vector3 nextVertex = outer[nextIndex];
        Vector3 nowVertex = outer[nowIndex];

        // Is it a reflection vertex?
        bool isReflex =! GeomUtil.IsAngleLessPI (nowVertex,
                                                prevVertex,
                                                nextVertex);

        // Does the triangle contain "reflection vertices"?
        if ((GeomUtil.IsInTriangle(outerP,
                                   triangle[0],
                                   triangle[1],
                                   triangle[2]) <= 0)&&(isReflex))
        {
            // Invisible because the vertices are included in the triangle

            // Find the angle between the M, I and M, outerP line segments (select the vertex with the shallowest angle)
            float angle = Vector3.Angle(intersectionPoint - M, outerP - M);
            if (angle < angleMin)
            {
                angleMin = angle;
                finalIndex = nowIndex;
            }
        }
    }
}

After finding the vertices (finalIndex) to join, join the vertex arrays of the inner and outer polygons.

  1. Create a list of new vertex arrays.
  2. Add up to the vertices (finalIndex) to join the outer polygons to the list.
  3. Add the inner polygonal vertex array to be joined to the list so that it goes around in order from vertex M.
  4. In order to connect the inner polygon to the outer polygon again, duplicate the vertex (finalIndex) to be combined with vertex M and add it to the list.
  5. Add the remaining outer polygon vertices to the list and you're done.

Second half of Combine Polygon

Vector3 FinalP = outer[finalIndex];

// Join (create a new polygon)
List<Vector3> newOuterVertices = new List<Vector3>();

// Add up to Index that divides outer
for (int i = 0; i <= finalIndex; i++)
{
    newOuterVertices.Add(outer[i]);
}

// Add all inner
for (int i = xmaxIndex; i < inner.vertices.Length; i++)
{
    newOuterVertices.Add(inner.vertices[i]);
}
for (int i = 0; i < xmaxIndex; i++)
{
    newOuterVertices.Add(inner.vertices[i]);
}

// Increase two vertices to split
newOuterVertices.Add(M);
newOuterVertices.Add(FinalP);

// Add the index of the remaining outer
for (int i = finalIndex + 1; i < outer.Length; i++)
{
    newOuterVertices.Add(outer[i]);
}

outer = newOuterVertices.ToArray();

Triangular division

When the inner and outer polygons become one polygon, it is finally divided into triangles. First, initialize the index array of vertices and create an ear list.

InitializeVertices function

/// <summary>
/// Initialization
/// </summary>
void InitializeVertices(Vector3[] points)
{
    vertices.Clear();
    indices.Clear();
    earTipList.Clear();

    // Create index array
    resultTriangulationOffset = resultVertices.Count;
    for (int i = 0; i < points.Length; i++)
    {
        Vector3 nowVertex = points[i];
        vertices.Add(nowVertex);

        indices.AddLast(i);

        resultVertices.Add(nowVertex);
    }

    // Search for convex triangles and ears
    LinkedListNode<int> node = indices.First;
    while (node != null)
    {
        CheckVertex(node);
        node = node.Next;
    }
}

The CheckVertex function that determines if a vertex is an ear looks like this:

CheckVertex function

void CheckVertex(LinkedListNode<int> node)
{
    // Convex vertex / Reflective vertex check
    int prevIndex = (node.Previous == null) ?
                      indices.Last.Value :
                      node.Previous.Value; // Previous vertex
    int nextIndex = (node.Next == null) ?
                      indices.First.Value :
                      node.Next.Value; // Next vertex
    int nowIndex = node.Value;

    Vector3 prevVertex = vertices[prevIndex];
    Vector3 nextVertex = vertices[nextIndex];
    Vector3 nowVertex = vertices[nowIndex];

    bool isEar = false;

    // Is the internal angle within 180 degrees?
    if (GeomUtil.IsAngleLessPI(nowVertex, prevVertex, nextVertex))
    {
        // Ear check
        // Within 180 degrees, the triangle does not contain other vertices
        isEar = true;
        foreach(int i in indices)
        {
            if ((i == prevIndex) || (i == nowIndex) || (i == nextIndex))
                continue;

            Vector3 p = vertices[i];

            // Ignore if the coordinates are the same duplicated at the time of division
            if (Vector3.Distance(p, prevVertex) <= float.Epsilon) continue;
            if (Vector3.Distance(p, nowVertex) <= float.Epsilon) continue;
            if (Vector3.Distance(p, nextVertex) <= float.Epsilon) continue;

            if(GeomUtil.IsInTriangle(p,
                                     prevVertex,
                                     nowVertex,
                                     nextVertex) <= 0)
            {
                isEar = false;
                break;
            }

        }
        if (isEar)
        {
            if (!earTipList.Contains(nowIndex))
            {
                // Add ears
                earTipList.Add(nowIndex);
            }
        }
        else
        {
            // Exclude if it is no longer an ear when it is already an ear
            if (earTipList.Contains(nowIndex))
            {
                // Ear removal
                earTipList.Remove(nowIndex);
            }
        }

    }

}

The actual triangulation is done in the following EarClipping function. As mentioned above, the vertices are taken out from the top of the ear list and the triangle connected to the front and back vertices is output. Then, the procedure of deleting the vertices of the ear from the vertex index array and determining whether the vertices before and after are the ears is repeated.

EarClipping function

void EarClipping()
{
    int triangleIndex = 0;

    while (earTipList.Count > 0)
    {
        int nowIndex = earTipList [0]; // Extract top

        LinkedListNode<int> indexNode = indices.Find(nowIndex);
        if (indexNode != null)
        {
            int prevIndex = (indexNode.Previous == null) ?
                              indices.Last.Value :
                              indexNode.Previous.Value; // Previous vertex
            int nextIndex = (indexNode.Next == null) ?
                              indices.First.Value :
                              indexNode.Next.Value; // Next vertex

            Vector3 prevVertex = vertices[prevIndex];
            Vector3 nextVertex = vertices[nextIndex];
            Vector3 nowVertex = vertices[nowIndex];

            // Triangle creation
            triangles.Add(new Triangle(
              prevVertex,
              nowVertex,
              nextVertex, "(" + triangleIndex + ")"));

            resultTriangulation.Add(resultTriangulationOffset + prevIndex);
            resultTriangulation.Add(resultTriangulationOffset + nowIndex);
            resultTriangulation.Add(resultTriangulationOffset + nextIndex);

            triangleIndex++;

            if (indices.Count == 3)
            {
                // End because it is the last triangle
                break;
            }

            // Delete ear vertices
            earTipList.RemoveAt (0); // Remove top
            indices.Remove(nowIndex);

            // Check the vertices before and after
            int[] bothlist = { prevIndex, nextIndex };
            for (int i = 0; i < bothlist.Length; i++)
            {
                LinkedListNode<int> node = indices.Find(bothlist[i]);
                CheckVertex(node);
            }
        }
        else
        {
            Debug.LogError("index now found");
            break;
        }
    }

    // UV calculation
    for (int i = 0; i < vertices.Count; i++)
    {
        Vector2 uv2 = CalcUV(vertices[i], uvRect);
        resultUVs.Add(uv2);
    }
}

Mesh generation

Make the result of triangle division into Mesh. In the EarClipping function, we prepare the necessary vertex array and index array (resultVertices and resultTriangulation) and pour them into Mesh.

MakeMesh function

void MakeMesh()
{
    mesh = new Mesh();
    mesh.vertices = resultVertices.ToArray();
    mesh.SetIndices(resultTriangulation.ToArray(),
                    MeshTopology.Triangles, 0);
    mesh.RecalculateNormals();
    mesh.SetUVs(0, resultUVs);

    mesh.RecalculateBounds();

    MeshFilter filter = GetComponent<MeshFilter>();
    if(filter != null)
    {
        filter.mesh = mesh;
    }
}

By the way, I also set the UV coordinates. UV coordinates are assigned within the rectangular area of ​​the polygon.

CalcUV

Vector2 CalcUV(Vector3 vertex, Rect uvRect)
{
    float u = (vertex.x - uvRect.x) / uvRect.width;
    float v = (vertex.y - uvRect.y) / uvRect.height;

    return new Vector2(u, v);
}

5.7  Summary

I have explained the polygonal triangle division by the ear cutting method. It is important to use it, but if you draw a figure with the mouse, it will become a mesh in real time, you can make the outline data of the font a mesh, etc., but since it is not such a fast algorithm, if the number of vertices increases, it will be speedy. There will be problems (calculated in order by the CPU), but I find it interesting that complex polygons can be divided into triangles with a simple algorithm.

See 5.8 

https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf

https://ja.wikipedia.org/wiki/ Polygon triangulation